/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.editpart;

import java.util.Iterator;
import java.util.List;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.ToolbarLayout;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.commands.UnexecutableCommand;
import org.eclipse.gef.editpolicies.FlowLayoutEditPolicy;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.gef.requests.GroupRequest;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.NamedElement;

import com.cea.papyrus.core.commands.AddUMLElementToDiagram;
import com.cea.papyrus.core.commands.Di2UMLElementCreateCommand;
import com.cea.papyrus.core.commands.FeatureAddChildCommand;
import com.cea.papyrus.core.commands.FeatureMoveChildCommand;
import com.cea.papyrus.core.commands.GraphNodeSetConstraintCommand;
import com.cea.papyrus.core.commands.OrphanFeatureChildCommand;
import com.cea.papyrus.core.commands.UMLElementAddCommand;
import com.cea.papyrus.core.dnd.ObjectTransfer;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * EditPolicy for the Figure used by ClassFeatureListEditPart.
 */
public class FeatureListFlowLayoutEditPolicy extends FlowLayoutEditPolicy {

	/**
	 * 
	 */
	private int channel=16;
	
	/**
	 * A Guard object is used to guard a portion of code.
	 * 
	 * @author dumoulin
	 */
	public interface Guard {
		
		/**
		 * Return true if the guard accept the specified object.
		 * 
		 * @param object 
		 * 
		 * @return 
		 */
		public boolean accept(Object object);
	}

	/**
	 * 
	 */
	private class ClassTypeGuard implements Guard {
		
		/**
		 * 
		 */
		private Class type;
		
		/**
		 * 
		 * 
		 * @param type 
		 */
		public ClassTypeGuard( Class type) {
			this.type = type;
		}
		
		/**
		 * 
		 * 
		 * @param object 
		 * 
		 * @return 
		 */
		public boolean accept(Object object) {
			return type.isAssignableFrom( (Class)object );
		}
	}
	
	/**
	 * 
	 */
	private Guard guard;


	/**
	 * 
	 */
	public FeatureListFlowLayoutEditPolicy()
	{
		guard = new Guard(){
			public boolean accept(Object object){
				return true;
			}
		};
	}

	/**
	 * 
	 * 
	 * @param elementType 
	 */
	public FeatureListFlowLayoutEditPolicy(Class elementType)
	{
		if(elementType==null) {
			throw new IllegalArgumentException("elementType should be set");
		}
		guard = new ClassTypeGuard(elementType);
	}

	/**
	 * 
	 * 
	 * @param guard 
	 */
	public FeatureListFlowLayoutEditPolicy(Guard guard)
	{
		this.guard = guard;
	}

	/**
	 * 
	 * 
	 * @param request 
	 * 
	 * @return 
	 */
	@Override
	public Command getCommand(Request request) {
		return super.getCommand(request);
	}

	/**
	 * 
	 * 
	 * @return <code>true</code> if the host's LayoutManager is in a
	 * horizontal orientation
	 */
	@Override
	protected boolean isHorizontal() {
		IFigure figure = ((GraphicalEditPart) getHost()).getContentPane();
		return ((ToolbarLayout) figure.getLayoutManager()).isHorizontal();
		// return false;
	}

	/**
	 * * The child is moved inside the container. I.e. the child position
	 * change.
	 * Returns the <code>Command</code> to move the specified
	 * child before the given reference <code>EditPart</code>. If the
	 * reference is <code>null</code>, the child should be moved in front
	 * of all children.
	 * <P>
	 * A move is a change in the order of the children, which indirectly
	 * causes a change in location on the screen.
	 * 
	 * @param child the child being moved
	 * @param after <code>null</code> or the EditPart that should be after
	 * (or to the right of) the child being moved
	 * 
	 * @return a Command to move the child
	 */
	@Override
	protected Command createMoveChildCommand(EditPart child, EditPart after) {
		GraphNode childModel = (GraphNode)child.getModel();
		GraphNode afterModel = null;
		if (after != null) {
			afterModel = (GraphNode)after.getModel();
		}

		return new FeatureMoveChildCommand((GraphNode) getHost().getModel(),
				childModel, afterModel);

	}

	/**
	 * * The child is added inside the container.
	 * 
	 * @param child 
	 * @param after 
	 * 
	 * @return 
	 */
	@Override
	protected Command createAddCommand(EditPart child, EditPart after) {
		Debug.debug(this,"FeatureListFlowLayoutEditPolicy.createAddCommand", channel);
		CompoundCommand command= new CompoundCommand();
		GraphNode graphNodeChild = (GraphNode)child.getModel();
		GraphNode graphNodeAfter = null;
		GraphNode graphNodeParent = (GraphNode) getHost().getModel();

		Element umlChild=null;
		Element umlParent=null;

		//1. create graphNode Command
		if (after != null) {
			graphNodeAfter = (GraphNode)after.getModel();
		}

		command.add(new FeatureAddChildCommand(graphNodeParent,
				graphNodeChild, graphNodeAfter));

		//2. create UML Command
		//look for uml child
		if( graphNodeChild.getSemanticModel()!=null &&
				((Uml1SemanticModelBridge)graphNodeChild.getSemanticModel()).getElement()!=null){
			umlChild=((Uml1SemanticModelBridge)graphNodeChild.getSemanticModel()).getElement();
		}
		//look for UML parent
		GraphNode UMLParentGraphNode=(GraphNode)graphNodeParent.getContainer();
		if( UMLParentGraphNode.getSemanticModel()!=null &&
				((Uml1SemanticModelBridge)UMLParentGraphNode.getSemanticModel()).getElement()!=null){
			umlParent=((Uml1SemanticModelBridge)UMLParentGraphNode.getSemanticModel()).getElement();
		}
		
		// test guard
		if(umlChild != null) {
			if(!guard.accept(umlChild.getClass())) {
				return UnexecutableCommand.INSTANCE;
			}
		}
		
		//create the command
		if( umlChild!=null && umlParent!=null){
			command.add(new UMLElementAddCommand(umlParent,umlChild));}
		
		//3 update size
		command.add(child.getParent().getCommand(new Request(DimensionAdaptationEditPolicy.DIMENSION_ADAPTATION)));

		return command;


	}

	/**
	 * * A new child should be created and added.
	 * 
	 * @param request 
	 * 
	 * @return 
	 */
	@Override
	protected Command getCreateCommand(CreateRequest request) {
		Debug.debug(this,"FeatureListFlowLayoutEditPolicy.getCreateCommand", channel);
		if (request.getExtendedData().get(ObjectTransfer.UML_TRANSFER_OBJECT)!=null){
			CompoundCommand command= new CompoundCommand();
			List elementList= (List)request.getExtendedData().get(ObjectTransfer.UML_TRANSFER_OBJECT);
			Iterator it= elementList.iterator();
			int i =0;
			while (it.hasNext()){
				//Darpan
				Object o = it.next();
				if (!(o instanceof Element)) {
					return null;
				}
				//Darpan
				Element elt= (Element)it.next();
				GraphElement parent=(GraphElement)this.getHost().getModel();
				Point location= request.getLocation().getTranslated(i*120, 0);
				AddUMLElementToDiagram addIntoDiagram =new AddUMLElementToDiagram(elt,parent,location,null);
				i++;
				command.add(addIntoDiagram);
			}
			return command;
		}
		Object	newObjectType = request.getNewObjectType();

		// should test if new object type is compliant with the guard
		if(!guard.accept(newObjectType)) {
			return UnexecutableCommand.INSTANCE;
		}

		CompoundCommand command= new CompoundCommand();
		Di2UMLElementCreateCommand createCommand = null;

		//look for type diagram
//		TypeDiagram type=TypeDiagram.CLASS_DIAGRAM_LITERAL;
//		MultiDiagramEditor mde = (MultiDiagramEditor) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
//		DiagramEditorPage cde = (DiagramEditorPage) mde.getActiveEditor();
//		if (cde != null) {
//		Diagram dia = cde.getDiagramModel();
//		type= dia.getType();
//		}
		//Darpan
		String type = null;
		if (this.getHost().getRoot().getContents() instanceof DiagramEditPart) {
			type = ((DiagramEditPart) this.getHost().getRoot().getContents()).getDiagram().getType();
		} else {
			EditPart parent = this.getHost().getParent();
			int i = 0; // dont want a stackoverflow here
			while (!(parent.getModel() instanceof Diagram) && i < 1000) {
				parent = parent.getParent();
				++i;
			}
			type = ((Diagram) parent.getModel()).getType();
		}
		//Darpan
		//create the command to create a uml elment associated to his di2
		createCommand = new Di2UMLElementCreateCommand((java.lang.Class)newObjectType,
				(GraphNode)getHost().getModel(),
				type);
		createCommand.setLocation(request.getLocation());
		command.add(createCommand);
		GraphNode gp=((GraphNode)this.getHost().getParent().getModel());
		
		command.add(new GraphNodeSetConstraintCommand(gp,
				new ChangeBoundsRequest(),
				new Rectangle(gp.getPosition().x,gp.getPosition().y,gp.getSize().width,gp.getSize().height+20)));

		return command;
	}

	/**
	 * Returns the <code>Command</code> to delete a child.
	 * 
	 * @param request the Request
	 * 
	 * @return the Command to delete the child
	 */
	@Override
	protected Command getDeleteDependantCommand(Request request) {
		Debug.debug(this,"FeatureListFlowLayoutEditPolicy.getDeleteDependantCommand", channel);
		return null;
	}

	/**
	 * Returns the <code>Command</code> to orphan a group of children. The contribution to
	 * orphan might contain two parts, both of which are optional. The first part is to
	 * actually remove the children from their existing parent. Some application models will
	 * perform an orphan implicitly when the children are added to their new parent. The
	 * second part is to perform some adjustments on the remaining children. For example, a
	 * Table layout might simplify itself by collapsing any unused columns and rows.
	 * 
	 * @param request the Request
	 * 
	 * @return <code>null</code> or a Command to perform an orphan
	 */
	@Override
	protected Command getOrphanChildrenCommand(Request request) {
		Debug.debug(this,"FeatureListFlowLayoutEditPolicy.getOrphanChildrenCommand", channel);
		List parts =((GroupRequest)request).getEditParts();



		CompoundCommand result = new CompoundCommand();

		for (int i = 0; i < parts.size(); i++) {
			OrphanFeatureChildCommand orphan = new OrphanFeatureChildCommand();
			GraphNode graphNodeChild =(GraphNode)((EditPart)parts.get(i)).getModel();
			GraphNode graphNodeParent = (GraphNode)getHost().getModel();

			Element umlChild=null;
			Element umlParent=null;

			orphan.setChild(graphNodeChild);
			orphan.setParent(graphNodeParent);
			orphan.setLabel("Mouvment");
			result.add(orphan);

			//2. create UML Command
			//look for uml child
			if( graphNodeChild.getSemanticModel()!=null &&
					((Uml1SemanticModelBridge)graphNodeChild.getSemanticModel()).getElement()!=null){
				umlChild=((Uml1SemanticModelBridge)graphNodeChild.getSemanticModel()).getElement();
				if(umlChild instanceof NamedElement) {
					orphan.setLabel("Mouvment of "+((NamedElement)umlChild).getName());
				}
			}
			//look for UML parent
			GraphNode UMLParentGraphNode=(GraphNode)graphNodeParent.getContainer();
			if( UMLParentGraphNode.getSemanticModel()!=null &&
					((Uml1SemanticModelBridge)UMLParentGraphNode.getSemanticModel()).getElement()!=null){
				umlParent=((Uml1SemanticModelBridge)UMLParentGraphNode.getSemanticModel()).getElement();
			}
			
			//create the command
			if( umlChild!=null && umlParent!=null){
				result.add(new UMLElementAddCommand(umlParent,umlChild));}

		}
		return result.unwrap();

	}
}